#!/usr/bin/python
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板 - SVN自动化部署插件 v1.1.0
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2025 BT-SOFT(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: CodeBuddy
# +-------------------------------------------------------------------

import os
import sys
import json
import subprocess
import time
import re
import shutil
from datetime import datetime

# 添加面板路径
panel_path = '/www/server/panel'
if panel_path not in sys.path:
    sys.path.insert(0, panel_path)

import public

class svn_auto_deploy_main:
    def __init__(self):
        self.svn_root = '/var/svn'
        self.plugin_path = '/www/server/panel/plugin/svn_auto_deploy'
        self.log_file = os.path.join(self.plugin_path, 'logs', 'svn.log')
        self.ip_cache_file = os.path.join(self.plugin_path, 'cache', 'ip_cache.json')
        self.operation_log_file = os.path.join(self.plugin_path, 'logs', 'operation.log')
        self.client_ip_cache_file = os.path.join(self.plugin_path, 'cache', 'client_ip_cache.json')
        
    def write_log(self, message, log_type="info", operation=None, status=None, details="", user=None):
        """统一日志写入方法
        
        Args:
            message: 日志消息内容
            log_type: 日志类型 (info, error, operation)
            operation: 操作名称 (当log_type为operation时使用)
            status: 操作状态 (成功/失败)
            details: 详细信息
            user: 操作用户
        """
        try:
            # 确保日志目录存在
            if not os.path.exists(os.path.dirname(self.log_file)):
                os.makedirs(os.path.dirname(self.log_file))
            if not os.path.exists(os.path.dirname(self.operation_log_file)):
                os.makedirs(os.path.dirname(self.operation_log_file))
                
            timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
            
            # 写入普通日志
            with open(self.log_file, 'a', encoding='utf-8') as f:
                f.write(f'[{timestamp}] {message}\n')
            
            # 如果是操作日志，同时写入操作日志文件
            if log_type == "operation" and operation and status:
                # 获取客户端IP作为用户标识
                if not user:
                    try:
                        # 尝试获取客户端IP
                        user = self.get_client_ip() or 'unknown'
                    except:
                        user = 'unknown'
                
                log_entry = {
                    'time': timestamp,
                    'operation': operation,
                    'status': status,
                    'details': details,
                    'user': user
                }
                
                with open(self.operation_log_file, 'a', encoding='utf-8') as f:
                    f.write(json.dumps(log_entry, ensure_ascii=False) + '\n')
                    
        except Exception as e:
            # 日志写入失败时静默处理
            pass
    
    def get_client_ip(self):
        """获取客户端IP地址，带缓存功能"""
        # 首先尝试从缓存读取IP
        cached_ip = self._read_client_ip_cache()
        if cached_ip and cached_ip != 'unknown':
            return cached_ip
        
        # 缓存不存在或已过期，重新获取IP
        ip = self._fetch_client_ip()
        
        # 将获取到的IP写入缓存（缓存一天）
        if ip and ip != 'unknown':
            self._write_client_ip_cache(ip)
        
        return ip
    
    def _read_client_ip_cache(self):
        """读取客户端IP缓存"""
        try:
            if not os.path.exists(self.client_ip_cache_file):
                return None
            
            with open(self.client_ip_cache_file, 'r', encoding='utf-8') as f:
                cache_data = json.load(f)
            
            # 检查缓存是否过期（一天 = 86400秒）
            current_time = time.time()
            if 'timestamp' in cache_data and 'ip' in cache_data:
                if current_time - cache_data['timestamp'] < 86400:  # 一天内
                    return cache_data['ip']
            
            return None
            
        except Exception as e:
            self.write_log(f"读取客户端IP缓存失败: {str(e)}", "error")
            return None
    
    def _write_client_ip_cache(self, ip):
        """写入客户端IP缓存"""
        try:
            # 确保缓存目录存在
            cache_dir = os.path.dirname(self.client_ip_cache_file)
            if not os.path.exists(cache_dir):
                os.makedirs(cache_dir, mode=0o755)
            
            # 写入缓存数据
            cache_data = {
                'ip': ip,
                'timestamp': time.time()
            }
            
            with open(self.client_ip_cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_data, f, ensure_ascii=False, indent=2)
            
            self.write_log(f"客户端IP缓存写入成功: {ip}")
            
        except Exception as e:
            self.write_log(f"写入客户端IP缓存失败: {str(e)}", "error")
    
    def _fetch_client_ip(self):
        """实际获取客户端IP地址"""
        try:
            # 尝试从HTTP请求头中获取客户端IP
            # 在宝塔面板环境中，可以通过以下方式获取客户端IP
            
            # 方法1：尝试从环境变量获取
            ip = os.environ.get('HTTP_X_FORWARDED_FOR')
            if ip:
                # 可能有多个IP，取第一个
                ip = ip.split(',')[0].strip()
                return ip
                
            # 方法2：尝试从其他环境变量获取
            ip = os.environ.get('HTTP_X_REAL_IP') or os.environ.get('REMOTE_ADDR')
            if ip:
                return ip
                
            # 方法3：尝试从公共变量获取
            if 'public' in globals():
                # 宝塔面板可能提供了获取客户端IP的方法
                try:
                    # 尝试从面板获取客户端IP
                    from flask import request
                    if request:
                        return request.remote_addr
                except:
                    pass
                    
                # 尝试从面板其他方法获取
                try:
                    ip = public.GetClientIp()
                    if ip:
                        return ip
                except:
                    pass
            
            # 如果所有方法都失败，返回服务器IP作为备用
            return self.get_server_ip()
            
        except:
            # 如果获取失败，返回服务器IP作为备用
            try:
                return self.get_server_ip()
            except:
                return 'unknown'
    
    def run_command(self, cmd):
        """执行系统命令"""
        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=30)
            return {
                'status': result.returncode == 0,
                'msg': result.stdout if result.returncode == 0 else result.stderr,
                'code': result.returncode
            }
        except subprocess.TimeoutExpired:
            return {'status': False, 'msg': '命令执行超时', 'code': -1}
        except Exception as e:
            return {'status': False, 'msg': str(e), 'code': -1}
    
    def check_svn_installed(self):
        """检查SVN是否已安装"""
        result = self.run_command('which svnserve')
        return result['status']
    
    def get_svn_version(self):
        """获取SVN版本"""
        if not self.check_svn_installed():
            return "未安装"
        result = self.run_command('svnserve --version | head -n 1')
        if result['status']:
            return result['msg'].strip()
        return "获取版本失败"
    
    def install_svn(self):
        """安装SVN"""
        self.write_log("开始安装SVN")
        
        # 检查系统类型
        system_type = ""
        if os.path.exists('/etc/redhat-release'):
            cmd = 'yum install -y subversion'
            system_type = "CentOS/RHEL"
        elif os.path.exists('/etc/debian_version'):
            cmd = 'apt-get update && apt-get install -y subversion'
            system_type = "Ubuntu/Debian"
        else:
            system_type = "未知系统"
            self.write_log(f"安装SVN失败: 不支持的系统类型 - {system_type}", "error", "安装SVN", "失败", f"不支持的系统类型: {system_type}")
            return {'status': False, 'msg': '不支持的系统类型'}
        
        result = self.run_command(cmd)
        if result['status']:
            self.write_log("SVN安装成功", "operation", "安装SVN", "成功", f"系统类型: {system_type}")
            return {'status': True, 'msg': 'SVN安装成功'}
        else:
            error_msg = result['msg']
            self.write_log(f"SVN安装失败: {error_msg}", "error", "安装SVN", "失败", f"系统类型: {system_type}, 错误: {error_msg}")
            return {'status': False, 'msg': f'SVN安装失败: {error_msg}'}
    
    def create_repository(self, project_name):
        """创建SVN仓库"""
        svn_path = os.path.join(self.svn_root, project_name)
        
        # 检查仓库是否已存在
        if os.path.exists(svn_path):
            self.write_log(f"创建仓库失败: 仓库 {project_name} 已存在", "error", "创建仓库", "失败", f"仓库 {project_name} 已存在")
            return {'status': False, 'msg': f'仓库 {project_name} 已存在'}
        
        # 创建SVN根目录
        if not os.path.exists(self.svn_root):
            os.makedirs(self.svn_root, mode=0o755)
        
        # 创建仓库
        result = self.run_command(f'svnadmin create "{svn_path}"')
        if result['status']:
            self.write_log(f"创建仓库成功: {project_name}", "operation", "创建仓库", "成功", f"仓库路径: {svn_path}")
            return {'status': True, 'msg': f'仓库 {project_name} 创建成功'}
        else:
            error_msg = result['msg']
            self.write_log(f"创建仓库失败: {error_msg}", "error", "创建仓库", "失败", f"仓库: {project_name}, 错误: {error_msg}")
            return {'status': False, 'msg': f'创建仓库失败: {error_msg}'}
    
    def configure_repository(self, project_name, users):
        """配置SVN仓库"""
        svn_path = os.path.join(self.svn_root, project_name)
        conf_path = os.path.join(svn_path, 'conf')
        
        if not os.path.exists(conf_path):
            return {'status': False, 'msg': '仓库配置目录不存在'}
        
        try:
            # 配置svnserve.conf
            svnserve_conf = os.path.join(conf_path, 'svnserve.conf')
            with open(svnserve_conf, 'w', encoding='utf-8') as f:
                f.write(f"""[general]
anon-access = none
auth-access = write
password-db = passwd
authz-db = authz
realm = /var/svn/{project_name}
""")
            
            # 配置passwd文件
            passwd_file = os.path.join(conf_path, 'passwd')
            with open(passwd_file, 'w', encoding='utf-8') as f:
                f.write('[users]\n')
                for user in users:
                    f.write(f"{user['username']} = {user['password']}\n")
            
            # 配置authz文件
            authz_file = os.path.join(conf_path, 'authz')
            with open(authz_file, 'w', encoding='utf-8') as f:
                f.write('[/]\n')
                for user in users:
                    f.write(f"{user['username']} = rw\n")
            
            self.write_log(f"配置仓库成功: {project_name}")
            return {'status': True, 'msg': '仓库配置成功'}
            
        except Exception as e:
            self.write_log(f"配置仓库失败: {str(e)}", "error")
            return {'status': False, 'msg': f'配置仓库失败: {str(e)}'}
    
    def configure_hook(self, project_name, web_dir, username, password):
        """配置自动同步钩子"""
        svn_path = os.path.join(self.svn_root, project_name)
        hook_path = os.path.join(svn_path, 'hooks', 'post-commit')
        
        try:
            # 创建网站目录
            if not os.path.exists(web_dir):
                os.makedirs(web_dir, mode=0o755)
            
            # 创建post-commit钩子
            hook_content = f"""#!/bin/sh
export LANG=en_US.utf8
SVN_PATH=/usr/bin/svn
WEB_PATH={web_dir}
$SVN_PATH update $WEB_PATH --username '{username}' --password '{password}' --no-auth-cache
"""
            
            with open(hook_path, 'w', encoding='utf-8') as f:
                f.write(hook_content)
            
            # 设置权限
            os.chmod(hook_path, 0o755)
            
            # 设置www用户权限
            result = self.run_command('id www')
            if result['status']:
                self.run_command(f'chown www:www "{hook_path}"')
            
            self.write_log(f"配置钩子成功: {project_name}")
            return {'status': True, 'msg': '自动同步钩子配置成功'}
            
        except Exception as e:
            self.write_log(f"配置钩子失败: {str(e)}", "error")
            return {'status': False, 'msg': f'配置钩子失败: {str(e)}'}
    
    def initial_checkout(self, project_name, web_dir, username, password):
        """初始检出"""
        try:
            # 检查目录是否已存在SVN工作副本
            if os.path.exists(os.path.join(web_dir, '.svn')):
                # 如果已存在工作副本，提示用户并停止操作
                self.write_log(f"检测到目录下已存在SVN工作副本，停止检出操作: {web_dir}", "error")
                return {'status': False, 'msg': f'目录 {web_dir} 下已存在SVN工作副本，请先清理或选择其他目录'}
            
            cmd = f'svn co "svn://localhost/{project_name}" "{web_dir}" --username "{username}" --password "{password}" --non-interactive --trust-server-cert'
            result = self.run_command(cmd)
            
            if result['status']:
                self.write_log(f"初始检出成功: {project_name}")
                return {'status': True, 'msg': '初始检出成功'}
            else:
                self.write_log(f"初始检出失败: {result['msg']}", "error")
                return {'status': False, 'msg': f'初始检出失败: {result["msg"]}'}
                    
        except Exception as e:
            self.write_log(f"初始检出异常: {str(e)}", "error")
            return {'status': False, 'msg': f'初始检出异常: {str(e)}'}
    
    def start_svn_service(self):
        """启动SVN服务"""
        try:
            # 停止现有服务
            self.run_command('killall svnserve')
            time.sleep(1)
            
            # 启动服务
            result = self.run_command(f'svnserve -d -r "{self.svn_root}"')
            if result['status'] or result['code'] == 0:
                self.write_log("SVN服务启动成功", "operation", "启动SVN服务", "成功", f"SVN根目录: {self.svn_root}")
                return {'status': True, 'msg': 'SVN服务启动成功'}
            else:
                error_msg = result['msg']
                self.write_log(f"SVN服务启动失败: {error_msg}", "error", "启动SVN服务", "失败", f"错误: {error_msg}")
                return {'status': False, 'msg': f'SVN服务启动失败: {error_msg}'}
                
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"启动SVN服务异常: {error_msg}", "error", "启动SVN服务", "失败", f"异常: {error_msg}")
            return {'status': False, 'msg': f'启动SVN服务异常: {error_msg}'}
    
    def stop_svn_service(self):
        """停止SVN服务"""
        try:
            result = self.run_command('killall svnserve')
            self.write_log("SVN服务已停止", "operation", "停止SVN服务", "成功", "")
            return {'status': True, 'msg': 'SVN服务已停止'}
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"停止SVN服务失败: {error_msg}", "error", "停止SVN服务", "失败", f"错误: {error_msg}")
            return {'status': False, 'msg': f'停止SVN服务失败: {error_msg}'}
    
    def check_port_status(self, port=3690):
        """检查端口状态"""
        try:
            import socket
            
            # 检查本地端口是否被占用
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(2)
            result = sock.connect_ex(('127.0.0.1', port))
            sock.close()
            
            if result == 0:
                # 端口被占用，检查是否是SVN服务
                result_cmd = self.run_command(f'netstat -tlnp | grep :{port}')
                if result_cmd['status']:
                    if 'svnserve' in result_cmd['msg']:
                        # 检查外网连通性
                        server_ip = self.get_server_ip()
                        if server_ip != 'localhost':
                            # 尝试从外网连接测试
                            try:
                                ext_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                                ext_sock.settimeout(3)
                                ext_result = ext_sock.connect_ex((server_ip, port))
                                ext_sock.close()
                                
                                if ext_result == 0:
                                    return f'开放 (SVN服务，外网可访问)'
                                else:
                                    # 检查防火墙状态
                                    firewall_result = self.run_command('systemctl status firewalld 2>/dev/null || systemctl status ufw 2>/dev/null || echo "no firewall"')
                                    if firewall_result['status']:
                                        if 'active (running)' in firewall_result['msg']:
                                            return f'开放 (SVN服务，但外网无法访问 - 防火墙可能阻止)'
                                        else:
                                            return f'开放 (SVN服务，但外网无法访问 - 云安全策略可能阻止)'
                                    else:
                                        return f'开放 (SVN服务，但外网无法访问 - 云安全策略可能阻止)'
                            except:
                                return f'开放 (SVN服务，外网连通性检测失败)'
                        else:
                            return '开放 (SVN服务)'
                    else:
                        return f'被占用 ({result_cmd["msg"].strip()})'
                else:
                    return '被占用 (未知进程)'
            else:
                # 检查防火墙是否开放端口
                firewall_check = self.run_command(f'iptables -L -n | grep :{port} || ufw status | grep {port} || echo "no firewall rule"')
                if firewall_check['status'] and 'no firewall rule' not in firewall_check['msg']:
                    return '未开放 (防火墙已配置但服务未启动)'
                else:
                    return '未开放'
                
        except Exception as e:
            return f'检查失败: {str(e)}'
    
    def get_svn_status(self):
        """获取SVN服务状态"""
        result = self.run_command('pgrep svnserve')
        return result['status']
    
    def get_server_ip(self):
        """获取服务器外网IP地址 - 带缓存功能"""
        try:
            # 首先尝试从缓存读取IP
            cached_ip = self._read_ip_cache()
            if cached_ip and cached_ip != 'localhost':
                self.write_log(f"从缓存读取IP: {cached_ip}")
                return cached_ip
            
            # 缓存不存在或已过期，重新获取IP
            ip = self._fetch_server_ip()
            
            # 将获取到的IP写入缓存
            if ip and ip != 'localhost':
                self._write_ip_cache(ip)
            
            return ip
            
        except Exception as e:
            self.write_log(f"获取服务器IP失败: {str(e)}", "error")
            return 'localhost'
    
    def _read_ip_cache(self):
        """读取IP缓存 - 永久缓存，不限制时长"""
        try:
            if not os.path.exists(self.ip_cache_file):
                return None
            
            with open(self.ip_cache_file, 'r', encoding='utf-8') as f:
                cache_data = json.load(f)
            
            # 直接返回缓存的IP，不检查过期时间
            if 'ip' in cache_data:
                self.write_log(f"从缓存读取IP: {cache_data['ip']}")
                return cache_data['ip']
            else:
                return None
                
        except Exception as e:
            self.write_log(f"读取IP缓存失败: {str(e)}", "error")
            return None
    
    def _write_ip_cache(self, ip):
        """写入IP缓存 - 简化结构，永久缓存"""
        try:
            # 确保缓存目录存在
            cache_dir = os.path.dirname(self.ip_cache_file)
            if not os.path.exists(cache_dir):
                os.makedirs(cache_dir, mode=0o755)
            
            # 简化缓存结构，只存储IP
            cache_data = {'ip': ip}
            
            with open(self.ip_cache_file, 'w', encoding='utf-8') as f:
                json.dump(cache_data, f, ensure_ascii=False, indent=2)
            
            self.write_log(f"IP缓存写入成功: {ip}")
            
        except Exception as e:
            self.write_log(f"写入IP缓存失败: {str(e)}", "error")
    
    def _fetch_server_ip(self):
        """实际获取服务器IP地址"""
        # 多个外网IP获取网站，按优先级排序
        ip_services = [
            'http://myip.ipip.net/',  # 优先使用ipip.net，国内访问稳定
            'http://ifconfig.me/',
            'http://ipinfo.io/ip',
            'http://api.ipify.org/',
            'http://checkip.amazonaws.com/',
            'http://icanhazip.com/',
            'http://ident.me/',
            'http://ipecho.net/plain'
        ]
        
        # 尝试每个服务获取IP
        for service in ip_services:
            try:
                # 设置超时时间，避免长时间等待
                result = self.run_command(f'curl -s --connect-timeout 5 -m 10 {service}')
                if result['status'] and result['msg'].strip():
                    ip = result['msg'].strip()
                    # 清理可能的HTML标签和多余内容
                    ip = re.sub(r'<[^>]+>', '', ip)  # 移除HTML标签
                    ip = re.sub(r'\s+', '', ip)     # 移除空白字符
                    
                    # 验证IP格式
                    if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip):
                        self.write_log(f"通过 {service} 成功获取外网IP: {ip}")
                        return ip
            except:
                continue  # 单个服务失败继续尝试下一个
        
        # 如果所有外网服务都失败，尝试获取内网IP
        self.write_log("所有外网IP服务均失败，尝试获取内网IP")
        
        # 多种内网IP获取方法
        internal_ip_commands = [
            "ip route get 1 | awk '{print $NF;exit}'",
            "hostname -I | awk '{print $1}'",
            "ip addr show | grep 'inet ' | grep -v '127.0.0.1' | head -n1 | awk '{print $2}' | cut -d'/' -f1"
        ]
        
        for cmd in internal_ip_commands:
            try:
                result = self.run_command(cmd)
                if result['status'] and result['msg'].strip():
                    ip = result['msg'].strip()
                    if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip):
                        self.write_log(f"获取内网IP成功: {ip}")
                        return ip
            except:
                continue
        
        # 如果都失败，返回localhost
        self.write_log("所有IP获取方法均失败，返回localhost")
        return 'localhost'
    
    def list_repositories(self):
        """列出所有SVN仓库"""
        try:
            if not os.path.exists(self.svn_root):
                return {'status': True, 'data': []}
            
                    
            # 获取服务器IP（放在循环外面，提高效率）
            server_ip = self.get_server_ip()
            
            repositories = []
            for item in os.listdir(self.svn_root):
                repo_path = os.path.join(self.svn_root, item)
                if os.path.isdir(repo_path) and os.path.exists(os.path.join(repo_path, 'conf')):
                    # 读取用户信息
                    users = []
                    passwd_file = os.path.join(repo_path, 'conf', 'passwd')
                    if os.path.exists(passwd_file):
                        try:
                            with open(passwd_file, 'r', encoding='utf-8') as f:
                                content = f.read()
                                for line in content.split('\n'):
                                    # 跳过注释行（以#开头）和空行
                                    if '=' in line and not line.strip().startswith('[') and not line.strip().startswith('#'):
                                        username = line.split('=')[0].strip()
                                        if username:
                                            users.append(username)
                        except:
                            pass
                    
                    repositories.append({
                        'name': item,
                        'path': repo_path,
                        'users': users,
                        'url': f'svn://{server_ip}/{item}',
                        'local_url': f'svn://localhost/{item}'
                    })
            
            return {'status': True, 'data': repositories}
            
        except Exception as e:
            self.write_log(f"获取仓库列表失败: {str(e)}", "error")
            return {'status': False, 'msg': f'获取仓库列表失败: {str(e)}'}
    
    """获取系统站点列表"""
    def get_site_list(self, get):
        try:
            # 尝试从宝塔数据库获取网站列表
            sites = public.M('sites').field('id,name,path').select()
            result = []
            for site in sites:
                result.append({
                    'id': site['id'],
                    'name': site['name'],
                    'path': site['path']
                })
            
            # 如果数据库中没有网站，尝试从文件系统获取
            if not result:
                websites_path = '/www/wwwroot/'
                if os.path.exists(websites_path):
                    for item in os.listdir(websites_path):
                        item_path = os.path.join(websites_path, item)
                        if os.path.isdir(item_path):
                            result.append({
                                'id': len(result) + 1,
                                'name': item,
                                'path': item_path
                            })
            
            return public.returnMsg(True, result)
        except Exception as e:
            # 如果出现异常，返回示例数据用于测试
            self.write_log(f"获取站点列表失败: {str(e)}", "error")
            result = [
                {'id': 1, 'name': '示例网站1', 'path': '/www/wwwroot/example1'},
                {'id': 2, 'name': '示例网站2', 'path': '/www/wwwroot/example2'}
            ]
            return public.returnMsg(True, result)
    
    
    def delete_repository(self, project_name, delete_svn_dirs=False):
        """删除SVN仓库
        
        Args:
            project_name: 项目名称
            delete_svn_dirs: 是否同时删除关联的.svn目录
        """
        try:
            repo_path = os.path.join(self.svn_root, project_name)
            if not os.path.exists(repo_path):
                self.write_log(f"删除仓库失败: 仓库 {project_name} 不存在", "error", "删除仓库", "失败", f"仓库 {project_name} 不存在")
                return {'status': False, 'msg': f'仓库 {project_name} 不存在'}
            
            # 如果需要，在删除仓库前先读取post-commit文件信息
            deleted_svn_count = 0
            post_commit_exists = True
            web_dir = None
            
            if delete_svn_dirs:
                hook_file = os.path.join(repo_path, 'hooks', 'post-commit')
                post_commit_exists = os.path.exists(hook_file)
                
                if post_commit_exists:
                    # 读取post-commit文件中的WEB_PATH
                    try:
                        with open(hook_file, 'r', encoding='utf-8') as f:
                            content = f.read()
                            for line in content.split('\n'):
                                line = line.strip()
                                if line.startswith('WEB_PATH='):
                                    path_value = line[9:].strip()
                                    if path_value.startswith('"') and path_value.endswith('"'):
                                        path_value = path_value[1:-1]
                                    elif path_value.startswith("'") and path_value.endswith("'"):
                                        path_value = path_value[1:-1]
                                    web_dir = path_value
                                    break
                    except Exception as e:
                        self.write_log(f"读取post-commit文件失败: {str(e)}", "error")
                        web_dir = None
            
            # 删除仓库
            shutil.rmtree(repo_path)
            
            # 记录删除的仓库信息，以便后续查找关联的.svn目录
            self.write_log(f"删除仓库成功: {project_name}", "operation", "删除仓库", "成功", f"仓库路径: {repo_path}")
            
            # 如果需要，删除关联的.svn目录
            if delete_svn_dirs and web_dir:
                svn_path = os.path.join(web_dir, '.svn')
                if os.path.exists(svn_path):
                    try:
                        shutil.rmtree(svn_path)
                        deleted_svn_count += 1
                        self.write_log(f"删除关联的.svn目录: {svn_path}", "info")
                    except Exception as e:
                        self.write_log(f"删除.svn目录失败: {svn_path}, 错误: {str(e)}", "error")
                else:
                    self.write_log(f"网站目录中不存在.svn目录: {svn_path}", "info")
            
            # 构建返回消息
            msg = f'仓库 {project_name} 删除成功'
            if delete_svn_dirs:
                if not post_commit_exists:
                    msg += '，未找到post-commit配置文件'
                elif not web_dir:
                    msg += '，post-commit文件中未找到WEB_PATH配置'
                elif deleted_svn_count > 0:
                    msg += f'，同时删除了 {deleted_svn_count} 个.svn目录'
                else:
                    msg += '，未找到关联的网站目录'
                
            self.write_log(f"删除仓库目录: {project_name}", "operation", "删除仓库", "成功", f"结果: {msg}")    
            return {'status': True, 'msg': msg}
            
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"删除仓库失败: {error_msg}", "error", "删除仓库", "失败", f"仓库: {project_name}, 错误: {error_msg}")
            return {'status': False, 'msg': f'删除仓库失败: {error_msg}'}
        
    def add_user(self, project_name, username, password, permission='rw'):
        """添加用户"""
        try:
            repo_path = os.path.join(self.svn_root, project_name)
            if not os.path.exists(repo_path):
                self.write_log(f"添加用户失败: 仓库 {project_name} 不存在", "error", "添加用户", "失败", f"仓库 {project_name} 不存在")
                return {'status': False, 'msg': f'仓库 {project_name} 不存在'}
            
            passwd_file = os.path.join(repo_path, 'conf', 'passwd')
            authz_file = os.path.join(repo_path, 'conf', 'authz')
            
            # 检查用户是否已存在
            if os.path.exists(passwd_file):
                with open(passwd_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                    # 过滤掉注释行后检查用户是否存在
                    for line in content.split('\n'):
                        if not line.strip().startswith('#') and f'{username} =' in line:
                            self.write_log(f"添加用户失败: 用户已存在 - 仓库: {project_name}, 用户: {username}", "error", "添加用户", "失败", f"仓库: {project_name}, 用户: {username}, 原因: 用户已存在")
                            return {'status': False, 'msg': f'用户 {username} 已存在'}
            
            # 添加到passwd文件
            with open(passwd_file, 'a', encoding='utf-8') as f:
                f.write(f'{username} = {password}\n')
            
            # 添加到authz文件（只有在有权限时才添加）
            if permission:  # 如果权限不为空
                with open(authz_file, 'a', encoding='utf-8') as f:
                    f.write(f'{username} = {permission}\n')
            
            # 权限描述映射
            permission_desc = {
                'rw': '读写',
                'r': '只读', 
                'w': '只写',
                '': '无权限'
            }.get(permission, permission)
            
            self.write_log(f"添加用户成功: {project_name}/{username} (权限: {permission_desc})", "operation", "添加用户", "成功", f"仓库: {project_name}, 用户: {username}, 权限: {permission_desc}")
            return {'status': True, 'msg': f'用户 {username} 添加成功 (权限: {permission_desc})'}
            
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"添加用户失败: {error_msg}", "error", "添加用户", "失败", f"仓库: {project_name}, 用户: {username}, 错误: {error_msg}")
            return {'status': False, 'msg': f'添加用户失败: {error_msg}'}
    
    def delete_user(self, project_name, username):
        """删除用户"""
        try:
            repo_path = os.path.join(self.svn_root, project_name)
            if not os.path.exists(repo_path):
                self.write_log(f"删除用户失败: 仓库 {project_name} 不存在", "error", "删除用户", "失败", f"仓库 {project_name} 不存在")
                return {'status': False, 'msg': f'仓库 {project_name} 不存在'}
            
            passwd_file = os.path.join(repo_path, 'conf', 'passwd')
            authz_file = os.path.join(repo_path, 'conf', 'authz')
            
            # 从passwd文件删除
            if os.path.exists(passwd_file):
                with open(passwd_file, 'r', encoding='utf-8') as f:
                    lines = f.readlines()
                with open(passwd_file, 'w', encoding='utf-8') as f:
                    for line in lines:
                        # 只删除匹配的用户行，保留注释行和其他用户行
                        if not (not line.strip().startswith('#') and line.startswith(f'{username} =')):
                            f.write(line)
            
            # 从authz文件删除
            if os.path.exists(authz_file):
                with open(authz_file, 'r', encoding='utf-8') as f:
                    lines = f.readlines()
                with open(authz_file, 'w', encoding='utf-8') as f:
                    for line in lines:
                        # 只删除匹配的用户权限行，保留注释行和其他用户的权限行
                        if not (not line.strip().startswith('#') and line.startswith(f'{username} =')):
                            f.write(line)
            
            self.write_log(f"删除用户成功: {project_name}/{username}", "operation", "删除用户", "成功", f"仓库: {project_name}, 用户: {username}")
            return {'status': True, 'msg': f'用户 {username} 删除成功'}
            
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"删除用户失败: {error_msg}", "error", "删除用户", "失败", f"仓库: {project_name}, 用户: {username}, 错误: {error_msg}")
            return {'status': False, 'msg': f'删除用户失败: {error_msg}'}
    
    def create_site(self, get):
        """一键创建SVN站点"""
        try:
            project_name = get.get('project_name', '').strip()
            web_dir = get.get('web_dir', '').strip()
            users_str = get.get('users', '').strip()
            auto_generate_user = get.get('auto_generate_user', '0') == '1'
            
            if not project_name:
                return public.returnMsg(False, '项目名称不能为空')
            
            if not web_dir:
                return public.returnMsg(False, '网站目录不能为空')
            
            # 如果启用了自动生成用户且用户信息为空，则自动生成
            if auto_generate_user and not users_str:
                import random
                import string
                # 生成随机密码
                password = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
                users_str = f"{project_name}:{password}"
            
            if not users_str:
                return public.returnMsg(False, '用户信息不能为空')
            
            # 解析用户信息 (格式: username1:password1,username2:password2)
            users = []
            try:
                for user_pair in users_str.split(','):
                    username, password = user_pair.split(':')
                    users.append({'username': username.strip(), 'password': password.strip()})
            except:
                self.write_log(f"创建站点失败: 用户信息格式错误 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: 用户信息格式错误")
                return public.returnMsg(False, '用户信息格式错误，正确格式: username1:password1,username2:password2')
            
            if not users:
                return public.returnMsg(False, '至少需要一个用户')
            
            user_list = ", ".join([u['username'] for u in users])
            
            # 检查SVN是否已安装
            if not self.check_svn_installed():
                install_result = self.install_svn()
                if not install_result['status']:
                    self.write_log(f"创建站点失败: SVN安装失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: SVN安装失败")
                    return public.returnMsg(False, install_result['msg'])
            
            # 创建仓库
            result = self.create_repository(project_name)
            if not result['status']:
                self.write_log(f"创建站点失败: 仓库创建失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: 仓库创建失败")
                return public.returnMsg(False, result['msg'])
            
            # 配置仓库
            result = self.configure_repository(project_name, users)
            if not result['status']:
                self.write_log(f"创建站点失败: 仓库配置失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: 仓库配置失败")
                return public.returnMsg(False, result['msg'])
            
            # 启动SVN服务
            result = self.start_svn_service()
            if not result['status']:
                self.write_log(f"创建站点失败: SVN服务启动失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: SVN服务启动失败")
                return public.returnMsg(False, result['msg'])
            
            # 配置自动同步钩子
            result = self.configure_hook(project_name, web_dir, users[0]['username'], users[0]['password'])
            if not result['status']:
                self.write_log(f"创建站点失败: 钩子配置失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: 钩子配置失败")
                return public.returnMsg(False, result['msg'])
            
            # 初始检出
            result = self.initial_checkout(project_name, web_dir, users[0]['username'], users[0]['password'])
            if not result['status']:
                self.write_log(f"创建站点失败: 初始检出失败 - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 原因: 初始检出失败")
                return public.returnMsg(False, result['msg'])
            
            self.write_log(f"创建站点成功: {project_name}", "operation", "创建站点", "成功", f"项目: {project_name}, 目录: {web_dir}, 用户: {user_list}")
            return public.returnMsg(True, f'SVN站点 {project_name} 创建成功！')
            
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"创建站点失败: {error_msg}", "error")
            self.write_log(f"创建站点异常: {error_msg} - 项目: {project_name}", "error", "创建站点", "失败", f"项目: {project_name}, 异常: {error_msg}")
            return public.returnMsg(False, f'创建站点失败: {error_msg}')

    def get_status(self, get):
        """获取插件状态"""
        return public.returnMsg(True, {
            'svn_installed': self.check_svn_installed(),
            'svn_version': self.get_svn_version(),
            'svn_running': self.get_svn_status(),
            'svn_root': self.svn_root,
            'port_status': self.check_port_status(3690),
            'server_ip': self.get_server_ip()
        })

    def get_repositories(self, get):
        """获取仓库列表"""
        result = self.list_repositories()
        if result['status']:
            return public.returnMsg(True, result['data'])
        else:
            return public.returnMsg(False, result['msg'])

    def remove_repository(self, get):
        """删除仓库"""
        project_name = get.get('project_name', '').strip()
        if not project_name:
            return public.returnMsg(False, '项目名称不能为空')
        
        # 获取是否同时删除.svn目录的参数
        delete_svn_dirs = get.get('delete_svn_dirs', '0') == '1'
        
        result = self.delete_repository(project_name, delete_svn_dirs)
        return public.returnMsg(result['status'], result['msg'])

    def add_repository_user(self, get):
        """添加仓库用户"""
        project_name = get.get('project_name', '').strip()
        username = get.get('username', '').strip()
        password = get.get('password', '').strip()
        permission = get.get('permission', 'rw').strip()  # 获取权限参数，默认为rw
        
        if not all([project_name, username, password]):
            return public.returnMsg(False, '参数不完整')
        
        result = self.add_user(project_name, username, password, permission)
        return public.returnMsg(result['status'], result['msg'])

    def remove_repository_user(self, get):
        """删除仓库用户"""
        project_name = get.get('project_name', '').strip()
        username = get.get('username', '').strip()
        
        if not all([project_name, username]):
            return public.returnMsg(False, '参数不完整')
        
        result = self.delete_user(project_name, username)
        return public.returnMsg(result['status'], result['msg'])

    def restart_svn(self, get):
        """重启SVN服务"""
        result = self.start_svn_service()
        return public.returnMsg(result['status'], result['msg'])

    def stop_svn(self, get):
        """停止SVN服务"""
        result = self.stop_svn_service()
        return public.returnMsg(result['status'], result['msg'])
    
    def get_operation_logs(self, get):
        """获取操作日志"""
        try:
            page = int(get.get('page', 1))
            page_size = int(get.get('page_size', 20))
            
            if not os.path.exists(self.operation_log_file):
                return public.returnMsg(True, {
                    'logs': [],
                    'total': 0,
                    'page': page,
                    'page_size': page_size,
                    'total_pages': 0
                })
            
            # 读取所有日志条目
            logs = []
            with open(self.operation_log_file, 'r', encoding='utf-8') as f:
                for line in f:
                    try:
                        log_entry = json.loads(line.strip())
                        logs.append(log_entry)
                    except:
                        continue
            
            # 按时间倒序排列
            logs.sort(key=lambda x: x['time'], reverse=True)
            
            # 计算分页
            total = len(logs)
            total_pages = (total + page_size - 1) // page_size if total > 0 else 0
            
            # 修正页码，确保不会超出范围
            if page > total_pages and total_pages > 0:
                page = total_pages
            
            start_index = (page - 1) * page_size
            end_index = start_index + page_size
            paginated_logs = logs[start_index:end_index]
            
            return public.returnMsg(True, {
                'logs': paginated_logs,
                'total': total,
                'page': page,
                'page_size': page_size,
                'total_pages': total_pages
            })
            
        except Exception as e:
            self.write_log(f"获取操作日志失败: {str(e)}", "error")
            return public.returnMsg(False, f'获取操作日志失败: {str(e)}')
    
    def clear_operation_logs(self, get):
        """清空操作日志"""
        try:
            # 确保日志目录存在
            if not os.path.exists(os.path.dirname(self.operation_log_file)):
                os.makedirs(os.path.dirname(self.operation_log_file))
            
            # 清空日志文件
            with open(self.operation_log_file, 'w', encoding='utf-8') as f:
                f.write('')
            
            self.write_log("操作日志已清空", "operation", "清空日志", "成功", "")
            return public.returnMsg(True, '操作日志已清空')
            
        except Exception as e:
            error_msg = str(e)
            self.write_log(f"清空操作日志失败: {error_msg}", "error", "清空日志", "失败", f"错误: {error_msg}")
            return public.returnMsg(False, f'清空操作日志失败: {error_msg}')